﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

using Microsoft.Scripting;
using Microsoft.Scripting.Ast;
using Microsoft.Scripting.Utils;
using System.Collections;
using Scala.Compiler.Binding;
using Scala.Compiler.Types;

namespace Scala.Compiler.Ast
{
    public class DefDefStatement : Statement
    {
        private readonly ScalaScope _scope;
        private readonly ModifierFlags _mods;
        protected Statement _rhs;
        protected readonly string _name;
        private readonly IList<Parameter[]> _parameters;
        private bool _isLambda;

        internal bool _hasReturn;
        private int _headerIndex;

        private static int _lambdaId;

        public DefDefStatement(string name, IList<Parameter[]> parameters) : this(default(ModifierFlags), name, parameters, null) { }

        public DefDefStatement(ModifierFlags mods, string name, IList<Parameter[]> parameters, Statement body)
        {
            ContractUtils.RequiresNotNullItems(parameters, "parameters");

            if (name == null)
            {
                _name = "<anon$" + Interlocked.Increment(ref _lambdaId) + ">";
                _scope = new ScalaScope(_name);
                _isLambda = true;
            }
            else
            {
                _name = name;
                _scope = new ScalaScope(_name);
            }
            _scope.IsFunction = true;
            _parameters = parameters;
            _rhs = body;
        }

        public ScalaScope Scope { get { return _scope; } }
        public ScalaFunction ScalaFunction { get; set; }

        public bool IsLambda { get { return _isLambda; } }

        public IList<Parameter[]> Parameters { get { return _parameters; } }

        internal string[] ParameterNames
        {
            get
            {
                var r = from t in AllParams select (Parameter)t;
                return ArrayUtils.ConvertAll(r.ToArray(), val => val.Name);
            }
        }

        internal int ArgCount
        {
            get
            {
                return AllParams.Count();
            }
        }

        public Statement Body
        {
            get { return _rhs; }
            set { _rhs = value; }
        }

        public SourceLocation Header { get { return GlobalParent.IndexToLocation(_headerIndex); } }

        public int HeaderIndex
        {
            get { return _headerIndex; }
            set { _headerIndex = value; }
        }

        public string Name
        {
            get
            {
                return _name;
            }
        }

        internal IList<Parameter> AllParams
        {
            get
            {
                var res = _parameters.SelectMany(x => x is Array ? ((IEnumerable)x).Cast<object>() : Enumerable.Repeat(x, 1));
                var t = from r in res select (Parameter)r;
                return t.ToArray();
            }
        }

        public override void Walk(ScalaWalker walker)
        {
            if (walker.Walk(this))
            {
                if (_parameters != null)
                {
                    foreach (Parameter p in AllParams)
                    {
                        p.Walk(walker);
                    }
                }
                if (_rhs != null)
                {
                    _rhs.Walk(walker);
                }
            }
            walker.PostWalk(this);
        }

        public override Microsoft.Scripting.Ast.Expression Reduce()
        {
            IList<Microsoft.Scripting.Ast.ParameterExpression> paramList = new List<Microsoft.Scripting.Ast.ParameterExpression>();
            
            foreach (var p in AllParams)
            {
                p.Reduce();
                paramList.Add(p.ScalaVariable.ParamExpression);
            }
            Expression.Lambda(Expression.Block(Body.Reduce()), paramList).CompileToMethod(ScalaFunction.MethodBuilder);
            return Expression.Empty();
        }
    }
}
